package com.zym.init.core.aspect;

import com.alibaba.fastjson2.JSON;
import com.lzhpo.tracer.TracerConstants;
import com.zym.init.core.annotation.Log;
import com.zym.init.core.utils.IpUtils;
import com.zym.init.core.utils.RequestUtils;
import com.zym.init.core.utils.SecurityUtils;
import com.zym.init.mapper.OperationLogMapper;
import com.zym.init.model.po.OperationLogPo;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Aspect
@Component
public class LogAspect {

    @Resource
    OperationLogMapper operationLogMapper;

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 线程本地变量
     */
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    /**
     * 单线程化的线程池
     */
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    /**
     * 声明切面点拦截那些类
     */
    @Pointcut("@annotation(com.zym.init.core.annotation.Log)")
    private void pointCutMethodController() {
    }

    /**
     * 环绕通知前后增强
     */
    @Around(value = "pointCutMethodController()")
    public Object doAroundService(ProceedingJoinPoint joinPoint) throws Throwable {
        // 开始时间
        threadLocal.set(System.currentTimeMillis());
        // 执行方法
        Object result = joinPoint.proceed();
        // 保存日志
        recordLog(joinPoint, null);
        // 返回结果
        return result;
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        recordLog(joinPoint, e);
    }

    /**
     * 记录日志信息
     *
     * @param joinPointObj joinPoint
     * @param e            Exception 错误异常
     */
    private void recordLog(Object joinPointObj, final Exception e) {
        try {
            long beginTime = threadLocal.get();
            long endTime = System.currentTimeMillis();
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes != null) {
                // 取得请求对象
                HttpServletRequest request = requestAttributes.getRequest();

                // 获取当前的用户
                Long adminId = SecurityUtils.getUserId();

                // 获取日志注解
                ProceedingJoinPoint joinPoint = (ProceedingJoinPoint) joinPointObj;
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                Method method = signature.getMethod();
                Log logAnnotation = method.getAnnotation(Log.class);

                // 方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();

                // 获取请求参数
                String queryString = request.getQueryString();
                Object[] args = joinPoint.getArgs();
                String params = "";
                if (args.length > 0) {
                    if ("POST".equals(request.getMethod())) {
                        if (logAnnotation.isFile()) {
                            //文件类型获取文件名称作为参数
                            MultipartFile multipartFile = (MultipartFile) args[0];
                            StringBuilder sb = new StringBuilder();
                            // 如果参数大于1
                            if (args.length > 1) {
                                for (int i = 0; i < args.length; i++) {
                                    if (i != 0) {
                                        sb.append("；").append("第").append(i + 1).append("个参数：").append(JSON.toJSONString(args[i]));
                                    } else {
                                        // 获取文件名称
                                        sb.append("文件名称：").append(multipartFile.getOriginalFilename());
                                    }
                                }
                            }
                            params = sb.toString();
                        } else {
                            params = JSON.toJSONString(args);
                        }

                    } else if ("GET".equals(request.getMethod())) {
                        params = queryString;
                    }
                }

                // 错误信息
                String error = "";
                String status = "1";
                if (e != null) {
                    error = e.getMessage();
                    status = "2"; // 1=成功, 2=失败
                }

                // 数据库日志
                OperationLogPo model = new OperationLogPo();
                model.setUserId(adminId);
                model.setTitle(logAnnotation.title());
                model.setIp(IpUtils.getIpAddr());
                model.setType(request.getMethod());
                model.setMethod(className + "." + methodName + "()");
                model.setUrl(RequestUtils.route());
                model.setArgs(params);
                model.setError(error);
                model.setStatus(status);
                model.setStartTime(new Date(beginTime));
                model.setEndTime(new Date(endTime));
                model.setTaskTime(endTime - beginTime);
                model.setCreateTime(new Date());
                model.setTraceId(MDC.get(TracerConstants.X_B3_TRACE_ID));
                executor.submit(() -> {
                    operationLogMapper.insert(model);
                });
            }
        } catch (Exception ex) {
            log.error("操作日志记录失败", ex);
        }
    }

}
